#include <jni.h>
#include <string>

#ifdef ANDROID
#include <jni.h>
#include <android/log.h>
#define LOGE(format, ...)  __android_log_print(ANDROID_LOG_ERROR, "xohnffmpeg", format, ##__VA_ARGS__)
#define LOGI(format, ...)  __android_log_print(ANDROID_LOG_INFO,  "xohnffmpeg", format, ##__VA_ARGS__)
#else
#define LOGE(format, ...)  printf("xohnffmpeg " format "\n", ##__VA_ARGS__)
#define LOGI(format, ...)  printf("xohnffmpeg " format "\n", ##__VA_ARGS__)
#endif


/**
 * Split R, G, B planes in RGB24 file.
 * @param jstr_url  Location of Input RGB file.
 * @param w    Width of Input RGB file.
 * @param h    Height of Input RGB file.
 * @param num  Number of frames to process.
 * 分离RGB24像素数据中的R、G、B分量
 * RGB24格式的每个像素的三个分量是连续存储的。一帧宽高分别为w、h的RGB24图像一共占用w*h*3 Byte的存储空间。
 * RGB24格式规定首先存储第一个像素的R、G、B，然后存储第二个像素的R、G、B…以此类推。
 * 类似于YUV420P的存储方式称为Planar方式，而类似于RGB24的存储方式称为Packed方式
 */
extern "C" JNIEXPORT jint JNICALL
Java_com_xohn_ffmpeg_AVUtils_rgb24Split(
        JNIEnv *env,jobject /* this */,
        jstring jstr_url, jint w, jint h,jint num) {
    char input_url[100] = {0};
    const char *str = env->GetStringUTFChars(jstr_url, NULL);
    sprintf(input_url,"%s",str);
    env->ReleaseStringUTFChars(jstr_url,str);

    FILE *fp = fopen(input_url,"rb+");
    if(fp == NULL) return -1;

    strcpy(input_url+strlen(input_url)-4,"_r.y");
    FILE *fp1 = fopen(input_url,"wb+");
    strcpy(input_url+strlen(input_url)-4,"_g.y");
    FILE *fp2 = fopen(input_url,"wb+");
    strcpy(input_url+strlen(input_url)-4,"_b.y");
    FILE *fp3 = fopen(input_url,"wb+");

    unsigned char *pic=(unsigned char *)malloc(w*h*3);

    for(int i = 0;i < num;i++){

        fread(pic,1,w*h*3,fp);

        for(int j = 0;j < w*h*3;j = j+3){
            //R
            fwrite(pic+j,1,1,fp1);
            //G
            fwrite(pic+j+1,1,1,fp2);
            //B
            fwrite(pic+j+2,1,1,fp3);
        }
    }

    free(pic);
    fclose(fp);
    fclose(fp1);
    fclose(fp2);
    fclose(fp3);

    return 0;
}


/**
 * Convert RGB24 file to BMP file
 * @param jstr_rgb24path    Location of input RGB file.
 * @param width        Width of input RGB file.
 * @param height       Height of input RGB file.
 * @param jstr_bmppath      Location of Output BMP file.
 * 将RGB24格式像素数据封装为BMP图像
 * BMP采用的是小端（Little Endian）存储方式。这种存储方式中“RGB24”格式的像素的分量存储的先后顺序为B、G、R。由于RGB24格式存储的顺序是R、G、B，所以需要将“R”和“B”顺序作一个调换再进行存储
 * 该程序完成了主要完成了两个工作：
 * 1)将RGB数据前面加上文件头。
 * 2)将RGB数据中每个像素的“B”和“R”的位置互换。
 *  BMP文件是由BITMAPFILEHEADER、BITMAPINFOHEADER、RGB像素数据共3个部分构成，它的结构如下所示。
 * BITMAPFILEHEADER
 * BITMAPINFOHEADER
 * RGB像素数据
 */
extern "C" JNIEXPORT jint JNICALL
Java_com_xohn_ffmpeg_AVUtils_rgb24ToBmp(
        JNIEnv *env,jobject /* this */,
        jstring jstr_rgb24path, jint width, jint height,jstring jstr_bmppath) {
    typedef struct
    {
        long imageSize;
        long blank;
        long startPosition;
    }BmpHead;

    typedef struct
    {
        long  Length;
        long  width;
        long  height;
        unsigned short  colorPlane;
        unsigned short  bitColor;
        long  zipFormat;
        long  realSize;
        long  xPels;
        long  yPels;
        long  colorUse;
        long  colorImportant;
    }InfoHead;

    int i = 0,j = 0;
    BmpHead m_BMPHeader={0};
    InfoHead  m_BMPInfoHeader={0};
    char bfType[2] = {'B','M'};
    int header_size = sizeof(bfType)+sizeof(BmpHead)+sizeof(InfoHead);
    unsigned char *rgb24_buffer = NULL;
    FILE *fp_rgb24 = NULL,*fp_bmp = NULL;

    const char *rgb24path = env->GetStringUTFChars(jstr_rgb24path, NULL);
    if((fp_rgb24=fopen(rgb24path,"rb")) == NULL){
        LOGE("Error: Cannot open input RGB24 file.\n");
        env->ReleaseStringUTFChars(jstr_rgb24path,rgb24path);
        return -1;
    }
    env->ReleaseStringUTFChars(jstr_rgb24path,rgb24path);
    const char *bmppath = env->GetStringUTFChars(jstr_bmppath, NULL);
    if((fp_bmp=fopen(bmppath,"wb")) == NULL){
        LOGE("Error: Cannot open output BMP file.\n");
        env->ReleaseStringUTFChars(jstr_bmppath,bmppath);
        return -1;
    }

    rgb24_buffer = (unsigned char *)malloc(width*height*3);
    fread(rgb24_buffer,1,width*height*3,fp_rgb24);

    m_BMPHeader.imageSize = 3*width*height+header_size;
    m_BMPHeader.startPosition = header_size;

    m_BMPInfoHeader.Length = sizeof(InfoHead);
    m_BMPInfoHeader.width = width;
    //BMP storage pixel data in opposite direction of Y-axis (from bottom to top).
    m_BMPInfoHeader.height = -height;
    m_BMPInfoHeader.colorPlane = 1;
    m_BMPInfoHeader.bitColor = 24;
    m_BMPInfoHeader.realSize = 3*width*height;

    fwrite(bfType,1,sizeof(bfType),fp_bmp);
    fwrite(&m_BMPHeader,1,sizeof(m_BMPHeader),fp_bmp);
    fwrite(&m_BMPInfoHeader,1,sizeof(m_BMPInfoHeader),fp_bmp);

    //BMP save R1|G1|B1,R2|G2|B2 as B1|G1|R1,B2|G2|R2
    //It saves pixel data in Little Endian
    //So we change 'R' and 'B'
    for(j = 0;j < height;j++){
        for(i = 0;i < width;i++){
            char temp = rgb24_buffer[(j*width+i)*3+2];
            rgb24_buffer[(j*width+i)*3+2] = rgb24_buffer[(j*width+i)*3+0];
            rgb24_buffer[(j*width+i)*3+0] = temp;
        }
    }
    fwrite(rgb24_buffer,3*width*height,1,fp_bmp);
    fclose(fp_rgb24);
    fclose(fp_bmp);
    free(rgb24_buffer);
    LOGI("Finish generate %s!\n",bmppath);
    env->ReleaseStringUTFChars(jstr_bmppath,bmppath);
    return 0;
}



unsigned char clip_value(unsigned char x,unsigned char min_val,unsigned char  max_val){
    if(x > max_val){
        return max_val;
    }else if(x < min_val){
        return min_val;
    }else{
        return x;
    }
}

//RGB to YUV420
bool RGB24_TO_YUV420(unsigned char *RgbBuf,int w,int h,unsigned char *yuvBuf){
    unsigned char*ptrY, *ptrU, *ptrV, *ptrRGB;
    LOGI("pic_rgb24 [ ");
    ptrRGB = RgbBuf;
    for(int ll = 0;ll < w*h*3;ll++){
        LOGI("%c ",ptrRGB++);
        if(ll%50 == 0){LOGI("\n");}
    }
    LOGI("]\n");
    memset(yuvBuf,0,w*h*3/2);
    ptrY = yuvBuf;
    ptrU = yuvBuf + w*h;
    ptrV = ptrU + (w*h*1/4);
    unsigned char y, u, v, r, g, b;
    for (int j = 0; j < h;j++){
        ptrRGB = RgbBuf + w*j*3 ;
        //ptrRGB = RgbBuf + w * (h - 1 - j) * 3 ;
        for (int i = 0;i < w;i++){

            r = *(ptrRGB++);
            g = *(ptrRGB++);
            b = *(ptrRGB++);
            /*y = (unsigned char)( ( 66 * r + 129 * g +  25 * b + 128) >> 8) + 16  ;
            u = (unsigned char)( ( -38 * r -  74 * g + 112 * b + 128) >> 8) + 128 ;
            v = (unsigned char)( ( 112 * r -  94 * g -  18 * b + 128) >> 8) + 128 ;*/
            y = (unsigned char)( ( 66 * r + 129 * g +  25 * b) >> 8) + 16  ;
            u = (unsigned char)( ( -38 * r -  74 * g + 112 * b) >> 8) + 128 ;
            v = (unsigned char)( ( 112 * r -  94 * g -  18 * b) >> 8) + 128 ;
            *(ptrY++) = clip_value(y,0,255);
            if (j%2==0 && i%2 ==0){
                *(ptrU++) = clip_value(u,0,255);
            }
            else{
                if (i%2==0){
                    *(ptrV++) = clip_value(v,0,255);
                }
            }
        }
    }
    return true;
}

/**
 * Convert RGB24 file to YUV420P file
 * @param url_in  Location of Input RGB file.
 * @param w       Width of Input RGB file.
 * @param h       Height of Input RGB file.
 * @param num     Number of frames to process.
 * @param url_out Location of Output YUV file.
 * RGB24格式像素数据转换为YUV420P格式像素数据
 * RGB到YUV的转换公式：
 *           Y = 0.299*R+0.587*G+0.114*B
 *           U = -0.147*R-0.289*G+0.463*B
 *           V = 0.615*R-0.515*G-0.100*B
 * 转换的过程中有以下几点需要注意：
 * 1) RGB24存储方式是Packed，YUV420P存储方式是Planar。
 * 2) U，V在水平和垂直方向的取样数是Y的一半
 */
extern "C" JNIEXPORT jint JNICALL
Java_com_xohn_ffmpeg_AVUtils_rgb24ToYuv420(
        JNIEnv *env,jobject /* this */,
        jstring jstr_urlin, jint w, jint h, jint num,jstring jstr_urlout) {
    const char *url_in = env->GetStringUTFChars(jstr_urlin, NULL);
    LOGI("url_in : %s\n",url_in);
    FILE *fp = fopen(url_in,"rb+");
    env->ReleaseStringUTFChars(jstr_urlin,url_in);
    if(fp == NULL){
        return -1;
    }
    const char *urlout = env->GetStringUTFChars(jstr_urlout, NULL);
    LOGI("urlout : %s\n",urlout);
    FILE *fp1 = fopen(urlout,"wb+");
    env->ReleaseStringUTFChars(jstr_urlout,urlout);
    if(fp1 == NULL){
        return -1;
    }

    unsigned char *pic_rgb24 = (unsigned char *)malloc(w*h*3);
    unsigned char *pic_yuv420 = (unsigned char *)malloc(w*h*3/2);

    for(int i = 0;i < num;i++){
        fread(pic_rgb24,1,w*h*3,fp);
        RGB24_TO_YUV420(pic_rgb24,w,h,pic_yuv420);
        fwrite(pic_yuv420,1,w*h*3/2,fp1);
    }

    free(pic_rgb24);
    free(pic_yuv420);
    fclose(fp);
    fclose(fp1);

    return 0;
}




/**
 * Generate RGB24 colorbar.
 * @param width    Width of Output RGB file.
 * @param height   Height of Output RGB file.
 * @param url_out  Location of Output RGB file.
 * 生成RGB24格式的彩条测试图
 */
extern "C" JNIEXPORT jint JNICALL
Java_com_xohn_ffmpeg_AVUtils_rgb24ColorBar(
        JNIEnv *env,jobject /* this */,
        jint width, jint height,jstring jstr_urlout) {
    unsigned char *data = NULL;
    int barwidth;
    FILE *fp = NULL;
    int i = 0,j = 0;

    data = (unsigned char *)malloc(width*height*3);
    barwidth = width/8;

    const char *url_out = env->GetStringUTFChars(jstr_urlout, NULL);
    fp = fopen(url_out,"wb+");
    env->ReleaseStringUTFChars(jstr_urlout,url_out);
    if(fp == NULL){
        return -1;
    }

    for(j = 0;j < height;j++){
        for(i = 0;i < width;i++){
            int barnum = i/barwidth;
            switch(barnum){
                case 0:{
                    data[(j*width+i)*3+0] = 255;
                    data[(j*width+i)*3+1] = 255;
                    data[(j*width+i)*3+2] = 255;
                    break;
                }
                case 1:{
                    data[(j*width+i)*3+0] = 255;
                    data[(j*width+i)*3+1] = 255;
                    data[(j*width+i)*3+2] = 0;
                    break;
                }
                case 2:{
                    data[(j*width+i)*3+0] = 0;
                    data[(j*width+i)*3+1] = 255;
                    data[(j*width+i)*3+2] = 255;
                    break;
                }
                case 3:{
                    data[(j*width+i)*3+0] = 0;
                    data[(j*width+i)*3+1] = 255;
                    data[(j*width+i)*3+2] = 0;
                    break;
                }
                case 4:{
                    data[(j*width+i)*3+0] = 255;
                    data[(j*width+i)*3+1] = 0;
                    data[(j*width+i)*3+2] = 255;
                    break;
                }
                case 5:{
                    data[(j*width+i)*3+0] = 255;
                    data[(j*width+i)*3+1] = 0;
                    data[(j*width+i)*3+2] = 0;
                    break;
                }
                case 6:{
                    data[(j*width+i)*3+0] = 0;
                    data[(j*width+i)*3+1] = 0;
                    data[(j*width+i)*3+2] = 255;

                    break;
                }
                case 7:{
                    data[(j*width+i)*3+0] = 0;
                    data[(j*width+i)*3+1] = 0;
                    data[(j*width+i)*3+2] = 0;
                    break;
                }
            }

        }
    }
    fwrite(data,width*height*3,1,fp);
    fclose(fp);
    free(data);

    return 0;
}